using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Linq;
using Less.Core;
using Less.Core.Model;
using Less.Tools;
using PetaPoco;

/// <summary>
/// Base class for database access. Includes logging and profiling
/// </summary>
public class Database : PetaPoco.Database, IDatabase
{
    private DateTime _start;
    #region Constructors
    public Database()
    : base(LessDatabaseUtils.LessConnectionStringName)
    {
        LessDatabaseUtils.InitializeDatabase(this);
    }

    public Database(IDbConnection connection)
    : base(connection)
    {
    }

    public Database(string connectionString, string providerName)
    : base(connectionString, providerName)
    {
    }

    public Database(string connectionString, DbProviderFactory provider)
    : base(connectionString, provider)
    {
    }

    public Database(string connectionStringName)
    : base(connectionStringName)
    {
        if (connectionStringName == LessDatabaseUtils.LessConnectionStringName)
        {
            LessDatabaseUtils.InitializeDatabase(this);
        }
    }
    #endregion
    #region IDatabase CRUD Methods
    

    public List<T> SelectList<T>(string command, params object[] args)
    {
        List<T> list;
        try
        {
            list = base.Fetch<T>(command, args);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        Log.Metric(EntryCategories.Database, metricName: "RecordsSelected", metricValue: list.HasValue() ? list.Count.ToString() : "0");
        return list;
    }

    public Page<T> SelectPage<T>(int pageIndex, int pageSize,string command, params object[] args)
    {
        Page<T> page = base.Page<T>(pageIndex, pageSize, command, args);
        Log.Metric(EntryCategories.Database, metricName: "RecordsSelected", metricValue: page.Items.HasValue() ? page.Items.ToString() : "0");
        return page;
    }

    public T Select<T>(string comand, params object[] args) where T : class
    {
        T item = null;
        try
        {
            Log.Debug(comand);
            var results = SelectList<T>(comand, args);
            if ( results.HasValue() )
            {
                item = results.FirstOrDefault();
                if ( results.Count > 1 )
                    Log.Warning("Too many results for: " + ( comand ));
            }
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        Log.Metric(EntryCategories.Database, metricName: "RecordsSelected", metricValue: item != null ? "1" : "0");
        return item;
    }

    public object Insert(string tableName, object poco)
    {
        object result;
        try
        {
            result = base.Insert(tableName, "Id", poco);
        }
        catch ( Exception ex )
        {
            throw CreateDatabaseException(ex);
        }
        if ( !( poco is LogEntry ) )
        {
            Log.Metric(EntryCategories.Database, metricName: "RecordsInserted", metricValue: "1");
        }
        return result;
    }

    public int Update(string tableName, object poco)
    {
        int result = 0;
        try
        {
            result = base.Update(tableName, "Id", poco);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        if (result > 0)
        {
            Log.Metric(EntryCategories.Database, metricName: "RecordsUpdated", metricValue: "1");
        }
        return result;
    }

    public int Delete(string tableName, object poco)
    {
        int result = base.Delete(tableName, "Id", poco);
        if (result > 0)
        {
            Log.Metric(EntryCategories.Database, metricName: "RecordsDeleted", metricValue: "1");
        }
        return result;
    }

    public new T ExecuteScalar<T>(string sql, params object[] args)
    {
        T result;
        try
        {
            result = base.ExecuteScalar<T>(sql, args);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        return result;
    }

    public new int Execute(string sql, params object[] args)
    {
        int result;
        try
        {
            result = base.Execute(sql, args);
        }
        catch (Exception ex)
        {
            throw CreateDatabaseException(ex);
        }
        return result;
    }
    #endregion
    #region IDatabase Members
    public string ConnectionString
    {
        get
        {
            return base._connectionString; //Functions.GetPrivateValue((PetaPoco.Database)this, "_connectionString") as string;
        }
    }
    public string ProviderName
    {
        get
        {
            return base._providerName; //Functions.GetPrivateValue((PetaPoco.Database)this, "_ProviderName") as string;
        }
    }
    #endregion
    public override IDbConnection OnConnectionOpened(IDbConnection conn)
    {
        // ToDo: Crear comando para usar ReadUncommitted
        return base.OnConnectionOpened(conn);
    }

    public override void OnExecutingCommand(IDbCommand cmd)
    {
        if (Log.EnableMetrics)
        {
            this._start = DateTime.Now;
        }
    }

    public override void OnExecutedCommand(IDbCommand cmd)
    {
        if (Log.EnableMetrics)
        {
            double timeSpan = DateTime.Now.Subtract(this._start).TotalSeconds;
            if (timeSpan > 1)
            {
                Log.ProcessEnd(entryCategory:EntryCategories.Database, processName: LastCommand, metricName: "Time", metricValue: timeSpan);
            }
        }
    }

    private Exception CreateDatabaseException(Exception e)
    {
        int i = 2;
        string callingMethod;
        do
        {
            callingMethod = Functions.GetCallingMethodName(i);
            i++;
        }
        while (callingMethod.Contains("PetaPoco") || callingMethod.Contains("LessDatabase"));
        var sqlCommandEx = new SqlCommandException(LastCommand, callingMethod, e);
        return sqlCommandEx;
    }
}